package postgres

import (
	"context"
	"fmt"
	"quipus/internal/database/model"

	apb "quipus/proto/gen/assist"
	cpb "quipus/proto/gen/common"

	_ "github.com/jackc/pgx/v5/stdlib"
	_ "github.com/joho/godotenv/autoload"
	"github.com/pkg/errors"
	"gorm.io/gorm/clause"
)

func (p *PGDB) GetSubject(ctx context.Context, matter string) (*model.Subject, error) {
	p.query()
	qdo := q.WithContext(ctx)
	return qdo.Subject.Where(q.Subject.Matter.Eq(matter)).First()
}

func (p *PGDB) GetSubjectById(ctx context.Context, subjectId int32) (*model.Subject, error) {
	p.query()
	qdo := q.WithContext(ctx)
	return qdo.Subject.Where(q.Subject.ID.Eq(subjectId)).First()
}

func (p *PGDB) ListSubjects(ctx context.Context, find *apb.FindSubject) ([]*model.Subject, int32, error) {
	fmt.Print("sub find : ", find)
	p.query()
	qdo := q.WithContext(ctx)
	subject := qdo.Subject

	if find.Matter != "" {
		subject = subject.Where(q.Subject.Matter.Eq(find.Matter))
	}

	if find.KeyWords != nil {
		for _, kw := range find.KeyWords {
			subject = subject.Where(q.Subject.Matter.Like(fmt.Sprintf("%%%s%%", kw)))
		}
	}

	if find.TenantId != 0 {
		subject = subject.Where(q.Subject.TenantID.Eq(find.TenantId))
	}

	var loadFavor bool
	if find.Visibilities != nil {
		subjectV := qdo.Subject

		for _, v := range find.Visibilities {
			if v == cpb.Visibility_FAVOR {
				loadFavor = true
				continue
			}
			subjectV = subjectV.Where(q.Subject.Visibility.Eq(cpb.Visibility_name[int32(v)]))
		}

		if loadFavor {
			subjectF := qdo.Subject

			subjectF = subjectF.Join(&model.FavorSubject{}, q.Subject.ID.EqCol(q.FavorSubject.SubjectID))
			subject = subject.Where(subjectF.Or(subjectF))
		} else {
			subject = subject.Where(subjectV)
		}
	}

	limit := find.Limit
	if limit == 0 {
		limit = 10
	}
	subjects, count, err := subject.FindByPage(int(find.Offset), int(limit))
	if err != nil {
		return nil, 0, err
	}

	return subjects, int32(count), err
}

func (p *PGDB) ListFavorSubjects(ctx context.Context, find *apb.FindSubject) ([]*model.Subject, int32, error) {
	p.query()
	qdo := q.WithContext(ctx)
	subject := qdo.Subject
	subject = subject.Join(&model.FavorSubject{}, q.Subject.ID.EqCol(q.FavorSubject.SubjectID), q.FavorSubject.UserID.Eq(find.CurrentUserId))
	// subject.UnderlyingDB().AutoMigrate(&model.FavorSubject{})
	// subject = subject.Preload().Where(q.Subject.ID.EqCol(q.FavorSubject.SubjectID), q.FavorSubject.UserID.Eq(find.CurrentUserId))
	subjects, count, err := subject.FindByPage(int(find.Offset), int(find.Limit))
	if err != nil {
		return nil, 0, err
	}
	return subjects, int32(count), err
}

func (p *PGDB) FavorSubject(ctx context.Context, userId int32, subjectId int32) (*model.Subject, error) {
	p.query()
	qdo := q.WithContext(ctx)
	favor := qdo.FavorSubject

	upsert := &model.FavorSubject{
		UserID:    userId,
		SubjectID: subjectId,
	}

	err := favor.Clauses(clause.OnConflict{
		Columns:   []clause.Column{{Name: "user_id"}, {Name: "subject_id"}},
		DoUpdates: clause.AssignmentColumns([]string{"user_id", "subject_id"}),
	}).Create(upsert)

	if err != nil {
		return nil, err
	}
	return p.GetSubjectById(ctx, subjectId)
}

func (p *PGDB) UnFavorSubject(ctx context.Context, userId int32, subjectId int32) (*model.Subject, error) {
	p.query()
	qdo := q.WithContext(ctx)
	favor := qdo.FavorSubject

	if userId > 0 {
		favor = favor.Where(q.FavorSubject.UserID.Eq(userId))
	}
	if subjectId > 0 {
		favor = favor.Where(q.FavorSubject.SubjectID.Eq(subjectId))
	}
	_, err := favor.Delete()
	if err != nil {
		return nil, err
	}
	return p.GetSubjectById(ctx, subjectId)
}

func (p *PGDB) UpdateSubject(ctx context.Context, subject *model.Subject) (*model.Subject, error) {
	p.query()
	qdo := q.WithContext(ctx)
	_, e := qdo.Subject.Updates(subject)
	return subject, e
}

func (p *PGDB) CheckFavorSubject(ctx context.Context, userId int32, subjectId int32) (bool, error) {
	p.query()
	qdo := q.WithContext(ctx)
	favor := qdo.FavorSubject
	c, err := favor.Where(q.FavorSubject.UserID.Eq(userId), q.FavorSubject.SubjectID.Eq(subjectId)).Count()
	if err != nil {
		return false, err
	}
	return c > 0, nil
}

func (p *PGDB) CountSubjectFavors(ctx context.Context, subjectId int32) (int32, error) {
	p.query()
	qdo := q.WithContext(ctx)
	favor := qdo.FavorSubject
	c, err := favor.Where(q.FavorSubject.SubjectID.Eq(subjectId)).Count()
	if err != nil {
		return 0, err
	}
	return int32(c), nil
}

func (p *PGDB) CreateSubject(ctx context.Context, create *model.Subject) error {
	p.query()
	qdo := q.WithContext(ctx)
	err := qdo.Subject.Create(create)
	if err != nil {
		return errors.Wrap(err, "failed to create subject")
	}
	return nil
}

func (p *PGDB) DeleteSubject(ctx context.Context, id int32) error {
	p.query()
	qdo := q.WithContext(ctx)
	_, err := qdo.Subject.Where(q.Subject.ID.Eq(id)).Delete()

	if err != nil {
		return err
	}

	return nil
}
